package im.yangqiang.android.ui.widget.chart;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.drawable.NinePatchDrawable;
import android.util.AttributeSet;
import android.util.Log;


import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import im.yangqiang.android.ui.R;
import im.yangqiang.android.ui.widget.chart.AxisView;


/**
 * DotAxisView
 * Created by Carlton on 2016/5/5.
 */
public class DotAxisView extends AxisView
{
    private static final String TAG = "DotAxisView";
    private final boolean DEBUG = true;
    private Paint mPaint;

    private int mDataDrawColor;
    private int mDataLineColor;
    private int mDataLineSize;
    private int mBaselineColor;
    private int mBaseLineSize;
    private float mMinData = 0;
    private float mMaxData = 0;
    /**
     * 元数据
     */
    private List<List<P>> mSrcData;
    private NinePatchDrawable mBackground;
    private Bitmap mDataDotBitmap;
    private Bitmap mBaselineBitmap;
    private Bitmap mBaseDotBitmap;
    private Double[] mYAxisData;

    public DotAxisView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        init(context, attrs, defStyle);
    }

    public DotAxisView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        init(context, attrs, 0);
    }

    public DotAxisView(Context context)
    {
        super(context);
        init(context, null, 0);
    }

    private void init(Context context, AttributeSet attrs, int defStyle)
    {
        initResource();
        mPaint = new Paint();
        mPaint.setAntiAlias(true);

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DotAxisView);
        mDataDrawColor = typedArray.getColor(R.styleable.DotAxisView_data_draw_color, Color.CYAN);
        mDataLineColor = typedArray.getColor(R.styleable.DotAxisView_data_line_color, Color.BLACK);
        mBaselineColor = typedArray.getColor(R.styleable.DotAxisView_baseline_line_color, Color.GRAY);
        mDataLineSize = typedArray.getDimensionPixelSize(R.styleable.DotAxisView_data_line_size, 20);
        mBaseLineSize = typedArray.getDimensionPixelSize(R.styleable.DotAxisView_baseline_line_size, 20);
        typedArray.recycle();

        if (isInEditMode())
        {
            demo();
        }
    }

    private void initResource()
    {
        mBackground = (NinePatchDrawable) getResources().getDrawable(R.drawable.bg_axis);
        mDataDotBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_axis_data_dot);
        mBaselineBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_axis_bg_line);
        mBaseDotBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_axis_baseline_dot);
    }

    private void demo()
    {
        float[] srcData = new float[]{100, 200, 300, 100, 200, 100, 200, 200, 300};
        float[] baseData = new float[]{250, 250, 250, 250, 250, 250, 250, 250, 250};
        List<List<DotAxisView.P>> data = new ArrayList<>();

        ArrayList<DotAxisView.P> dataP = new ArrayList<>();
        for (int i = 0; i < srcData.length; ++i)
        {
            dataP.add(new DotAxisView.P("X" + i, srcData[i]));
        }
        ArrayList<DotAxisView.P> base = new ArrayList<>();
        for (int i = 0; i < baseData.length; ++i)
        {
            base.add(new DotAxisView.P("Y" + i, baseData[i]));
        }

        data.add(base);
        data.add(dataP);
        setData(data);
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);
        doDraw(canvas);
    }

    private void doDraw(Canvas canvas)
    {
        drawAxisBackground(canvas);
        if (mSrcData != null && mSrcData.size() > 0)
        {
            drawBackgroundLine(canvas, mSrcData.get(0));
            drawBaseData(canvas, mSrcData.get(0));
        }
        if (null != mSrcData && mSrcData.size() > 1)
        {
            for (int i = 1; i < mSrcData.size(); ++i)
            {
                drawData(canvas, mSrcData.get(i));
            }
        }
    }

    /**
     * 绘制背景
     *
     * @param canvas
     */
    private void drawAxisBackground(Canvas canvas)
    {
        if (mBackground != null)
        {
            float space = 1 * getResources().getDisplayMetrics().density + 0.5f;
            mBackground.setBounds((int) (getO().x - space), (int) (getContentTop() - space), (int) (getContentRight() + space), (int) (getO().y + space * 2));
            mBackground.draw(canvas);
        }
    }

    /**
     * 绘制数据的背景
     *
     * @param canvas
     * @param ps
     */
    private void drawData(Canvas canvas, List<P> ps)
    {
        List<PointF> points = convertPoints(ps);
        if (ps == null || ps.size() < 1 || points == null || points.isEmpty())
        {
            return;
        }
        Path path = new Path();
        // 绘制背景
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mDataDrawColor);
        path.moveTo(points.get(0).x, getO().y);
        path.lineTo(points.get(0).x, points.get(0).y);
        for (int i = 1; i < points.size(); ++i)
        {
            PointF[] ctrlPoint = getCtrlPoint(points, i - 1);
            path.cubicTo(ctrlPoint[0].x, ctrlPoint[0].y, ctrlPoint[1].x, ctrlPoint[1].y, points.get(i).x, points.get(i).y);
        }
        path.lineTo(points.get(points.size() - 1).x, getO().y);
        canvas.drawPath(path, mPaint);

        path.reset();
        // 绘制线
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(mDataLineColor);
        mPaint.setStrokeWidth(mDataLineSize);
        drawCubic(canvas, points, mPaint);

        // 绘制点
        final float halfPointSize = mDataDotBitmap.getHeight() * 0.5f;
        for (int i = 0; i < points.size(); ++i)
        {
            PointF p = points.get(i);
            canvas.drawBitmap(mDataDotBitmap, p.x - halfPointSize, p.y - halfPointSize, mPaint);
        }
    }

    /**
     * 绘制背景竖线
     *
     * @param canvas
     * @param ps
     */
    private void drawBackgroundLine(Canvas canvas, List<P> ps)
    {
        // 绘制背景线
        mPaint.setStrokeWidth(1);
        mPaint.setColor(Color.WHITE);
        List<PointF> baselinePoints = convertPoints(ps);
        for (int i = 0; i < baselinePoints.size(); ++i)
        {
            PointF p = baselinePoints.get(i);
            int dstHeight = (int) (getO().y - getContentTop() + 15);
            Bitmap bitmap = Bitmap.createScaledBitmap(mBaselineBitmap, mBaselineBitmap.getWidth(), dstHeight, false);
            canvas.drawBitmap(bitmap, p.x - mBaselineBitmap.getWidth() * 0.5f, getContentTop(), mPaint);
        }
    }

    /**
     * 绘制基线数据
     *
     * @param canvas
     * @param ps
     */
    private void drawBaseData(Canvas canvas, List<P> ps)
    {
        if (ps == null || ps.size() < 1)
        {
            return;
        }
        List<PointF> baselinePoints = convertPoints(ps);
        // 绘制链接线
        mPaint.setStrokeWidth(mBaseLineSize);
        mPaint.setColor(mBaselineColor);
        mPaint.setStyle(Paint.Style.STROKE);
        drawCubic(canvas, baselinePoints, mPaint);
        // 绘制点
        final float halfPointSize = mBaseDotBitmap.getHeight() * 0.5f;
        for (int i = 0; i < baselinePoints.size(); ++i)
        {
            PointF point = baselinePoints.get(i);
            canvas.drawBitmap(mBaseDotBitmap, point.x - halfPointSize, point.y - halfPointSize, mPaint);
        }
    }

    /**
     * 绘制三阶曲线
     *
     * @param canvas
     * @param points
     * @param paint
     */
    private void drawCubic(Canvas canvas, List<PointF> points, Paint paint)
    {
        if (points == null || points.isEmpty())
        {
            return;
        }
        Path path = new Path();
        path.moveTo(points.get(0).x, points.get(0).y);
        for (int i = 1; i < points.size(); ++i)
        {
            PointF[] ctrlPoint = getCtrlPoint(points, i - 1);
            path.cubicTo(ctrlPoint[0].x, ctrlPoint[0].y, ctrlPoint[1].x, ctrlPoint[1].y, points.get(i).x, points.get(i).y);
        }
        canvas.drawPath(path, paint);
    }

    /**
     * 转换点
     *
     * @param points 需要转换的点
     * @return 转换后的点
     */
    private List<PointF> convertPoints(List<P> points)
    {
        final List<PointF> result = new ArrayList<>();
        if (points == null || points.isEmpty())
        {
            return result;
        }
        ArrayList<Float> xAxis = getXAxis();
        for (int i = 0; i < points.size() && i < xAxis.size(); ++i)
        {
            result.add(new PointF(getOriginX(xAxis.get(i)), getOriginYValue(points.get(i).y)));
        }
        return result;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    /**
     * 获取Y轴上的数据相对于坐标轴的实际位置
     *
     * @param value
     * @return
     */
    protected float getOriginYValue(float value)
    {
        float maxY = 0;
        float minY = 0;
        if (null != mYAxisData && mYAxisData.length > 0)
        {
            maxY = mYAxisData[mYAxisData.length - 1].floatValue();
            minY = mYAxisData[0].floatValue();
        }
        return getMinY() + getOriginY(value, mMaxData, mMinData, maxY, minY);
    }

    /**
     * 获取小的Y的值
     *
     * @return
     */
    private float getMinY()
    {
        return 0;
    }

    /**
     * 获取大的Y的值
     *
     * @return
     */
    private float getMaxY()
    {
        return 0;
    }

    /**
     * 设置数据
     *
     * @param data
     */
    public void setData(List<List<P>> data)
    {
        mSrcData = data;
        if (DEBUG)
        {
            String debug = "";
            for (int i = 0; i < data.size(); ++i)
            {
                debug += "\n";
                for (int j = 0; j < data.get(i).size(); ++j)
                {
                    debug += ("," + data.get(i).get(j).toString());
                }
            }
            Log.d(TAG, "图表数据:" + debug);
        }
        setXAxisData();
        setYAxisData();
        requestLayout();
    }

    public void setYFix(String prefix, String suffix)
    {
        mYPrefix = prefix;
        mYSuffix = suffix;
    }

    public List<List<P>> getData()
    {
        return mSrcData;
    }

    /**
     * 设置X轴的数据
     */
    private void setXAxisData()
    {
        if (null == mSrcData || mSrcData.size() < 1)
        {
            return;
        }
        final ArrayList<String> xAxisData = getXAxisData();
        xAxisData.clear();
        List<P> ps = mSrcData.get(0);
        for (int i = 0; i < ps.size(); ++i)
        {
            xAxisData.add(ps.get(i).getX());
        }
    }

    private String mYPrefix = "";
    private String mYSuffix = "";

    /**
     * 设置Y轴数据
     */
    private void setYAxisData()
    {
        if (null == mSrcData || mSrcData.size() < 1)
        {
            return;
        }
        // 计算需要比较次数
        int count = 0;
        for (int i = 0; i < mSrcData.size(); ++i)
        {
            count += mSrcData.get(i).size();
        }
        // 比较大小计算出来Y的份数
        // 把所有的Y值按照从小变大的顺序存储
        float[] tempY = new float[count];
        int preCount = mSrcData.get(0).size();
        for (int i = 0; i < count; ++i)
        {
            List<P> list = mSrcData.get(i / preCount);
            tempY[i] = list.get(i % preCount).getY();
        }
        // 冒泡排序
        for (int i = 0; i < tempY.length; ++i)
        {
            for (int j = i; j < tempY.length; ++j)
            {
                if (tempY[i] > tempY[j])
                {
                    float t = tempY[j];
                    tempY[j] = tempY[i];
                    tempY[i] = t;
                }
            }
        }
        final float min = tempY[0];
        final float max = tempY[tempY.length - 1];
        mMaxData = max;
        mMinData = min;
        ArrayList<String> yAxisData = getYAxisData();
        yAxisData.clear();
        Double[] yData = getYData(min, max);
        mYAxisData = yData;
        DecimalFormat format = new DecimalFormat("0.##");
        for (Double aYData : yData)
        {
            yAxisData.add(mYPrefix + format.format(aYData) + mYSuffix);
        }

    }

    private float getDiv(float value, int threshold)
    {
        if (value <= threshold)
        {
            return 1;
        }
        int result = 0;
        float temp = value;
        for (int i = 0; i < String.valueOf((int) value).length(); ++i)
        {
            if (temp > threshold)
            {
                ++result;
                temp /= 10;
            } else
            {
                break;
            }
        }
        float v = 10.0f * result;
        return v == 0 ? 1 : v;
    }

    private Double[] getYData(float min, float max)
    {
        float div = getDiv(max, 100);
        min = min / div;
        max = max / div;

        List<Double> tempArray = new ArrayList<>();
        int maxSeg = 5;
        int[] steps = {1, 2, 5, 10, 20};
        double minInteger = Math.floor(min);
        double maxInteger = Math.ceil(max);
        double realMaxY, realMinY;
        double realSeg = 0;
        double ratio = 1;
        if (max - min <= 1)
        {
            ratio = 10.f;
            steps = new int[]{1, 2, 5};
            minInteger = Math.floor(min * ratio);
            maxInteger = Math.ceil(max * ratio);
        }
        int step = 0;
        for (int stp : steps)
        {
            if ((maxInteger - minInteger) / stp <= maxSeg)
            {
                step = stp;
                break;
            }
        }

        if (maxInteger % step == 0)
        {
            realMaxY = maxInteger / ratio;
        } else
        {
            realMaxY = (maxInteger + step - maxInteger % step) / ratio;
        }
        realMinY = (minInteger - minInteger % step) / ratio;
        double realStep = step / ratio;
        realSeg = (float) ((realMaxY - realMinY) / realStep);
        if (realSeg == 0)
        {
            if (realMinY - 1 > 0)
            {
                tempArray.add(((realMinY - 1) * div));
            } else
            {
                tempArray.add(0.0);
            }
        }

        for (int i = 0; i < realSeg + 1; i++)
        {
            tempArray.add((div * (realMinY + realStep * i)));
        }
        return tempArray.toArray(new Double[]{});
    }

    /**
     * 绘制点
     */
    public static class P
    {
        private final float y;
        private String x;

        public P(String x, float y)
        {
            this.x = x;
            this.y = y;
        }

        public float getY()
        {
            return y;
        }

        public String getX()
        {
            return x;
        }

        public void setX(String x)
        {
            this.x = x;
        }

        @Override
        public String toString()
        {
            return "P{" +
                    "y=" + y +
                    ", x='" + x + '\'' +
                    '}';
        }
    }
}
